/*LICENSE_BEGIN
 * ******************************************************************
 * All original parts of SoccerScope2003 have copyright (2003) by
 * Team YowAI, represented by Mr. Shuhei Shiota
 * The Department of Creative Informatics
 * The Graduate School of Information Science and Technology
 * The University of Tokyo
 * 
 * Additional classes (except libraries in /lib) were developed by 
 * the Dainamite Robocup-Team of the DAI-Labor, TU-Berlin 
 * (www.dai-labor.de)
 * 
 * Extended/new classes/packages are:
 *   soccerscope.analyzer.LogfileAnalyzer
 *   soccerscope.daistatistic.*
 *   soccerscope.daistatistic.charts.*
 *   soccerscope.daistatistic.tools.*
 *   soccerscope.db.*
 *   soccerscope.dialog.*
 *   soccerscope.model.PlayerWorldModel
 *   soccerscope.model.SimplePlayer
 *   soccerscope.net.AgentConnector
 *   soccerscope.training.*
 *   soccerscope.training.conditions.*
 * ******************************************************************
LICENSE_END*/
package soccerscope.model;

import java.util.Vector;

import soccerscope.util.geom.Geometry;
import soccerscope.util.geom.Line2f;
import soccerscope.util.geom.Point2f;
import soccerscope.util.geom.Vector2f;

public class LocalPlayer extends Player {
	public Vector unknownPlayers;

	public Vector enemyPlayers;

	public Vector friendlyPlayers;

	public int number;

	public LocalPlayer(int teamSide) {
		super(teamSide);
		this.unknownPlayers = new Vector();
		this.enemyPlayers = new Vector();
		this.friendlyPlayers = new Vector();
	}

	public void setPlayer(LocalPlayer p) {
		super.setPlayer(p);
		this.friendlyPlayers = p.friendlyPlayers;
		this.enemyPlayers = p.enemyPlayers;
		this.unknownPlayers = p.unknownPlayers;
	}

	@Override
	public boolean isOffside(float line) {
		return this.side == Team.LEFT_SIDE && this.pos.x > line
				|| this.side == Team.RIGHT_SIDE && this.pos.x < line;
	}

	@Override
	public boolean isKickable(Point2f ball) {
		return this.pos.distance(ball) <= (Param.BALL_SIZE + getSize() + HeteroParam
				.get(this.type).kickable_margin);
	}

	@Override
	public boolean isCatchable(Point2f ball) {
		return this.pos.distance(ball) <= Param.GOALIE_CATCHABLE_AREA_LENGTH;
	}

	@Override
	public boolean canTackle(Point2f ball) {
		Vector2f rb = toRelative(ball);
		return (rb.x > 0 && rb.x <= Param.TACKLE_DIST && Math.abs(rb.y) <= Param.TACKLE_WIDTH / 2);
	}

	@Override
	public void step() {
		this.pos.add(this.vel);
		this.pos.add(this.acc);
		this.vel.add(this.acc);
		this.vel.scale(HeteroParam.get(this.type).player_decay);
		this.acc.set(0, 0);
	}

	@Override
	public float getKickable() {
		return (Param.BALL_SIZE + getSize() + HeteroParam.get(this.type).kickable_margin);
	}

	@Override
	public float getMoveMax() {
		return (1 + Param.PLAYER_RAND)
				* HeteroParam.get(this.type).player_speed_max;
	}

	@Override
	public float getTurnAngle() {
		return 180 / (1 + HeteroParam.get(this.type).inertia_moment
				* this.vel.r());
	}

	@Override
	public float getDashAccelerationMax() {
		return HeteroParam.get(this.type).dash_power_rate
				* HeteroParam.get(this.type).effort_max * Param.POWER_MAX;
	}

	@Override
	public Vector2f getVector() {
		return Vector2f.polar2vector(getSize(), this.angle);
	}

	@Override
	public Line2f getLine() {
		Point2f t = new Point2f(this.pos);
		t.add(getVector());
		return new Line2f(this.pos, t);
	}

	@Override
	public Vector2f toRelative(Point2f p) {
		return new Vector2f(this.pos, p).rotate(-this.angle);
	}

	@Override
	public float getAngleFromBody(Point2f p) {
		return Geometry.normalizeAngle(this.pos.dir(p) - this.angle);
	}

	@Override
	public float getAngleFromNeck(Point2f p) {
		return Geometry.normalizeAngle(this.pos.dir(p) - this.angle
				- this.angleNeck);
	}

	@Override
	public Vector2f getDashAccelerate(float power) {
		// effort == 1 �Ȥ��Ʒ׻�
		return Vector2f.polar2vector(HeteroParam.get(this.type).dash_power_rate
				* power, this.angle);
	}

	@Override
	public Vector2f getKickAccelerate(Point2f ball, float power, float dir) {
		Vector2f diff = new Vector2f();
		diff.sub(ball, this.pos);
		float effectiveKickPowerRate = Param.KICK_POWER_RATE;
		effectiveKickPowerRate *= 1.0
				- (diff.length() - Param.BALL_SIZE - getSize())
				/ HeteroParam.get(this.type).kickable_margin * 0.25
				- Math.abs(Geometry.normalizeAngle(diff.th() - this.angle))
				/ 180.0 * 0.25;
		return Vector2f.polar2vector(effectiveKickPowerRate * power, Geometry
				.normalizeAngle(this.angle + dir));
	}

	@Override
	public Vector2f getFinalMove() {
		return Vector2f.polar2vector(calcFinalDistance(), this.vel.th());
	}

	@Override
	public float getTackleProb(Point2f p) {
		Vector2f tmp = toRelative(p);
		float prob = (float) (1.0 - (Math.pow(tmp.x / Param.TACKLE_DIST,
				Param.TACKLE_EXPONENT) + Math.pow(Math.abs(tmp.y)
				/ (Param.TACKLE_WIDTH / 2.0f), Param.TACKLE_EXPONENT)));
		return prob;
	}

	// ����t���®�� (a == 0)(t >= 0)
	@Override
	public float calcVelocityAfterNTime(int t) {
		float v = this.vel.r();
		float d = HeteroParam.get(this.type).player_decay;
		return v * (float) Math.pow(d, t);
	}

	// ����t��ΰ�ư��Υ (a == 0)(t >= 0)
	@Override
	public float calcDistanceAfterNTime(int t) {
		float v = this.vel.r();
		float d = HeteroParam.get(this.type).player_decay;
		return v * (1 - (float) Math.pow(d, t)) / (1 - d);
	}

	// ����t���®�� (a != 0)(t >= 0)
	@Override
	public float calcVelocityAfterNTime(float a, int t) {
		float d = HeteroParam.get(this.type).player_decay;
		return calcVelocityAfterNTime(t) + a
				* ((1 - (float) Math.pow(d, t + 1)) / (1 - d) - 1);
	}

	// ����t��ΰ�ư��Υ (a != 0)(t >= 0)
	@Override
	public float calcDistanceAfterNTime(float a, int t) {
		float d = HeteroParam.get(this.type).player_decay;
		return calcDistanceAfterNTime(t) + (a / (1 - d))
				* (t + 1 - (1 - (float) Math.pow(d, t + 1)) / (1 - d));
	}

	// �ǽ�®�� (t -> ��)(a != 0)
	@Override
	public float calcFinalVelocity(float a) {
		float d = HeteroParam.get(this.type).player_decay;
		return a * d / (1 - d);
	}

	// �ǽ���ư��Υ (t -> ��)(a == 0)
	@Override
	public float calcFinalDistance() {
		float v = this.vel.r();
		float d = HeteroParam.get(this.type).player_decay;
		return v / (1 - d);
	}

	// ®��vt��ã����Τˤ�������� (a == 0)(vt <
	// v)
	@Override
	public int calcTimeByVelocity(float vt) {
		// float v = this.vel.r();
		float d = HeteroParam.get(this.type).player_decay;
		return (int) Math.ceil(((float) Math.log(vt) / (float) Math.log(d))
				- ((float) Math.log(this.vel.r()) / (float) Math.log(d)));
	}

	// ��Υdist��ư����Τˤ�������� (a ==
	// 0)(dist >= 0)
	@Override
	public int calcTimeByDistance(float dist) {
		float v = this.vel.r();
		float d = HeteroParam.get(this.type).player_decay;
		return (int) Math.ceil(((float) Math.log(1 - dist * (1 - d) / v))
				/ ((float) Math.log(d)));
	}

	// ®��vt��ã����Τˤ�������� (a !=
	// 0)(max(a, v) < vt <= speed_max)
	@Override
	public int calcTimeByVelocity(float vt, float a) {
		float v = this.vel.r();
		float d = HeteroParam.get(this.type).player_decay;
		return (int) Math.ceil(((float) Math.log((vt - a / (1 - d) + a)
				/ (v / d - a / (1 - d))) / (float) Math.log(d)) - 1);
	}

	// ��Υdist��ư����Τˤ�������� (a !=
	// 0)(dist >= 0)
	@Override
	public int calcTimeByDistance(float dist, float a) {
		int time = (int) dist;
		float tmp_dist = calcDistanceAfterNTime(a, time);
		if (tmp_dist == dist)
			return time;
		else if (tmp_dist < dist) {
			while (tmp_dist < dist) {
				time++;
				tmp_dist = calcDistanceAfterNTime(a, time);
			}
		} else {
			while (tmp_dist > dist) {
				time--;
				tmp_dist = calcDistanceAfterNTime(a, time);
			}
			time++;
		}
		return time;
	}

	// ��Υdist��ư����Τ˺���¤�����������
	// (a != 0)(dist >= 0)
	// =�ȥåץ��ԡ��ɤξ��֤�t�ϥ%å��夷�Ƥ��������
	@Override
	public int estimateMinTimeByDistance(float dist, float a) {
		float d = HeteroParam.get(this.type).player_decay;
		return (int) Math.ceil(dist / (a + (a * d) / (1 - d)));
	}

	public static void main(String arg[]) {
		LocalPlayer p = new LocalPlayer(Team.NEUTRAL);
		float acc = p.getDashAccelerationMax();

		long time, diff;

		time = System.currentTimeMillis();
		for (int i = 0; i < 22; i++)
			for (float d = 0.0f; d < 100; d += 0.5f) {
				p.calcTimeByDistance(d, acc);
			}
		diff = System.currentTimeMillis();
		System.out.println("normal: " + (diff - time));

		time = diff;
		float table[] = new float[120];
		for (int i = 0; i < 120; i++)
			table[i] = p.calcDistanceAfterNTime(acc, i);
		diff = System.currentTimeMillis();
		System.out.println("make: " + (diff - time));

		time = diff;
		for (int i = 0; i < 22; i++)
			for (float d = 0.0f; d < 100; d += 0.5f) {
				p.search(table, d);
			}
		diff = System.currentTimeMillis();
		System.out.println("cache: " + (diff - time));
	}

	@Override
	public int search(float[] t, float key) {
		int idx = (int) key;
		float tmp = t[idx];
		if (tmp == key)
			return idx;
		else if (tmp < key) {
			while (tmp < key) {
				idx++;
				tmp = t[idx];
			}
		} else {
			while (tmp > key) {
				idx--;
				tmp = t[idx];
			}
			idx++;
		}
		return idx;
	}

}
